% written by Moshe Weinstock
% ==============================================================

syso(String):-
	format(user_output, String, _Args1),
	format(user_output, '\n', _Args2).
syso(String, Args):-
	format(user_output, String, Args),
	format(user_output, '\n', _Args).

% UTILITIES
% ---------
sort_threads_by_priority(Spec, ThreadsByPriority):-
	all_spec_bthreads(Spec, Threads),
	merge_sort_threads_by_priority(Spec,Threads, ThreadsByPriority).

mid_split(L,L1,L2):-
	mid_split_helper(L,L,L1,L2).
mid_split_helper([], L, [], L).
mid_split_helper([_], L, [], L).
mid_split_helper([_,_|Rest],[Item|L],[Item|L1],L2):-
	mid_split_helper(Rest,L,L1,L2).
	
merge_sort_threads_by_priority(_Spec,[],[]):-!. % empty list is already sorted
merge_sort_threads_by_priority(_Spec,[X],[X]):-!. % single element list is already sorted
merge_sort_threads_by_priority(Spec,List,Sorted):-
	List=[_,_|_],
	mid_split(List,L1,L2), % list with at least two elements is divided into two parts
	merge_sort_threads_by_priority(Spec,L1,Sorted1),
	merge_sort_threads_by_priority(Spec,L2,Sorted2), % then each part is sorted
	merge_threads_by_priority(Spec,Sorted1,Sorted2,Sorted). % and sorted parts are merged
merge_threads_by_priority(_Spec,[],L,L):-!.
merge_threads_by_priority(_Spec,L,[],L):-L\=[],!.
merge_threads_by_priority(Spec,[T1|L1],[T2|L2],[T1|List]):-
	bthread(Spec, T1, P1),
	bthread(Spec, T2, P2),
	P1>P2,!,
	merge_threads_by_priority(Spec,L1,[T2|L2],List).
merge_threads_by_priority(Spec,[T1|L1],[T2|L2],[T2|List]):-
	bthread(Spec, T1, P1),
	bthread(Spec, T2, P2),
	P1=<P2,!,
	merge_threads_by_priority(Spec,[T1|L1],L2,List).

term_expansion(idgen(Name), [IDValStartPred, IDgenPred, IDCleanPred]):-
	atomic_concat(Name, '_idgen', IDgenPredName),
	atomic_concat(Name, '_id_val', IDValName),
	atomic_concat(Name,'_clean', IDClean),
	dynamic(IDValName/1),
	dynamic(IDgenPredName/1),
	IDValStartPred =.. [IDValName, 0],
	IDValQueryPred =.. [IDValName, ID],
	IDgenPredHead =.. [IDgenPredName, ID],
	retractall(IDValQueryPred),
	retractall(IDgenPredHead),
	IDgenPred = (IDgenPredHead:-
		    IDValQueryPred,
		     retract(IDValQueryPred),
		     NextVal is ID + 1,
		     NextIDValPred =.. [IDValName, NextVal],
		     assert(NextIDValPred)),
	IDCleanPredHead =.. [IDClean],
	IDCleanPred = (IDCleanPredHead:-
		      retract(IDValQueryPred),
		       assert(IDValStartPred)).

key_args([_LastArg], []).
key_args([Key|Rest], [Key|RestOfKeys]):-
	key_args(Rest, RestOfKeys).
create_flat_rule(Name, List, Keys, FlatRule):-
	member(Item, List),
	atomic_concat(Name, '_ok', OK_pred),
	append(Keys, [Item], FlatRuleArgs),
	Success = apply(OK_pred, FlatRuleArgs),
	FlatRule =.. [Name|FlatRuleArgs],
	% syso('generated flat rule ~w\n', [FlatRule]),
	(Success -> syso('generated flat rule ~w', [FlatRule]);
	 syso('unable to generate flat rule for ~w', [FlatRule])),
	Success.
convert_list_to_flat_rule(Name,
			  (term_expansion(F, Terms):- F =.. [Name | Args],
			   length(Args, ArgCount),
			   nth1(ArgCount, Args, LastArg),
			   is_list(LastArg),
			   syso('flattening list rule: ~w', [F]),
			   key_args(Args, Keys),
			   findall(FlatRule,
				   create_flat_rule(Name, LastArg, Keys, FlatRule),
				   Terms))).
term_expansion(list_to_flat_rule(Name), FlatRuleTermExpansion):-
	       convert_list_to_flat_rule(Name, FlatRuleTermExpansion).


% DATA MODEL
% ----------
:- discontiguous bpspec/1, bevent/2, bthread/3,
	state/3, watch/4, request/4, block/4, start_state/3,
	transition/5.

% bpspec(Spec).
% bevent(Spec, E).
% external_event(Spec,E).
% bthread(Spec, T, Priority).
% state(Spec, T, S).
% transition(Spec, T, S1, S2, E).
% watch(Spec, T, S, E).
% request(Spec, T, S, E).
% block(Spec, T, S, E).
% start_state(Spec, T, S).

% Data Validators
bpspec_ok(_Spec).
bevent_ok(Spec, _E):- bpspec(Spec).
bthread_ok(Spec, _T):- bpspec(Spec).
state_ok(Spec, T, _S):- bthread(Spec, T).
transition_ok(Spec, T, S1, S2, E):-
	bevent(Spec, E),
	state(Spec, T, S1),
	state(Spec, T, S2).
watch_ok(Spec, T, S, E):- state(Spec, T, S), bevent(Spec, E).
request_ok(Spec, T, S, E):- state(Spec, T, S), bevent(Spec, E).
block_ok(Spec, T, S, E):- state(Spec, T, S), bevent(Spec, E).
start_state_ok(Spec, T, S):- state(Spec, T, S),
	\+ (state(Spec, T, Other), Other \= S, start_state(Spec, T, Other)).

% comfort rules
list_to_flat_rule(watch).
list_to_flat_rule(request).
list_to_flat_rule(block).

% Behavioural Programming engine
%--------------------------------
all_spec_events(Spec, Events):-
	findall(E,
		bevent(Spec,E),
		Events).

all_spec_bthreads(Spec, Threads):-
	findall(T,
		bthread(Spec, T, _P),
		Threads).

max_priority_thread(Spec, MaxPriThread):-
	all_spec_bthreads(Spec, Threads),
	max_priority_thread_helper(Spec, Threads, -1, MaxPriThread).
max_priority_thread_helper(_Spec, [], Max, _T):-
	Max =\= -1.
max_priority_thread_helper(Spec, [T|Rest], Max, T):-
	bthread(Spec, T, P),
	Max < P,
	max_priority_thread_helper(Spec, Rest, P, T).
max_priority_thread_helper(Spec, [_T|Rest], Max, Thread):-
	max_priority_thread_helper(Spec, Rest, Max, Thread).

watched_events(Spec, T, State, Watched):-
	findall(W,
		watch(Spec, T, State, W),
		Watched).

is_unblocked(Spec, StateVector, E):-
	\+ (member([Thread, State], StateVector),
	    block(Spec, Thread, State, E)).

next_state_vector(_Spec, _E, [], []).
next_state_vector(Spec, E, [[T,S]|BThreadStates], [[T,NextS]|NewBThreadStates]):-
	% member(T, Resumed), %% might be used with a -strict flag where requested
	% are not watched - depends on specific target platform convention
	(request(Spec, T, S, E);
	 watch(Spec, T, S, E)),
	!,
	transition(Spec, T, S, NextS, E),
	next_state_vector(Spec,
			  E,
			  BThreadStates,
			  NewBThreadStates).
next_state_vector(Spec, E, [[T,S]|BThreadStates], [[T,S]|NewBThreadStates]):-
	next_state_vector(Spec,
			  E,
			  BThreadStates,
			  NewBThreadStates).


bsynch(Spec, BThreadStates, ExternalEvents, NewBThreadStates, Resumed):-
	max_priority_thread(Spec, T),
	syso('maximum priority bthread selected is <~w>',[T]),
	member([T, State], BThreadStates),
	(request(Spec, T, State, R),
	 is_unblocked(Spec, BThreadStates, R),
	 \+ external_event(Spec,R) ->
	 Requested = [any, R], syso('requested event chosen is <~w>',[R]);
	 Requested = []),
	append(Requested, ExternalEvents, Contingency),
	findall(T,
		(watched_events(Spec, T, State, Watched),
		 member(E, Contingency),
		 member(E, Watched)),
		Resumed),
	!,
	next_state_vector(Spec, R, BThreadStates, NewBThreadStates).

superstepper(Spec,
	     BThreadStates,ExternalEvents,
	     BThreadStates,
	     Depth):-
	superstepper_helper(Spec,
			    BThreadStates,ExternalEvents,
			    BThreadStates,
			    Depth,
			    0).
superstepper_helper(_Spec,
		    _BThreadStates,_ExternalEvents,
		    _NewBThreadStates,
		    Depth,CurrentDepth):-
	Depth is CurrentDepth.
superstepper_helper(Spec,
	     BThreadStates,ExternalEvents,
	     NewBThreadStates,
	     Depth,CurrentDepth):-
	bsynch(Spec, BThreadStates, ExternalEvents, TmpNewBThreadStates, _Resumed),
	NewDepth is CurrentDepth +1,
	superstepper_helper(Spec,
		     TmpNewBThreadStates, ExternalEvents,
		     NewBThreadStates,
		     Depth, NewDepth).

% Priority calculation
:- assert(maxBThreadPriority(1000)).
assign_priorities_to_bthreads(Spec):-
	findall(bthread(Spec, T, P),
		(bthread(Spec, T, P),
		 \+ number(P)), %a bthread has been declared without concrete priority
		BThreadsWithoutSetPriority),
	forall(member(bthread(Spec, T, P), BThreadsWithoutSetPriority),
	       (incrementing_integer(P),
		syso('thread ~w has priority ~w set.\n',[T, P]),
		maxBThreadPriority(Max),
		P < Max)).
		

idgen(thread_priority_guard).

% AND over requested events

%==============================
read_bpspec(Filename):-
	syso('\nreading file ~w', [Filename]),
	syso('>>>>'),
	consult(Filename),
	syso('<<<<').

% :- read_bpspec(roach_brains).
:- syso('BP engine loaded.\n').